package com.sxpi.order.service.order;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import com.sxpi.common.exception.PugBusinessException;
import com.sxpi.common.result.R;
import com.sxpi.order.aop.MessageAspect;
import com.sxpi.order.async.MessageAsyncService;
import com.sxpi.order.mode.event.OrderEvent;
import com.sxpi.order.feign.api.CourseApiFeign;
import com.sxpi.order.feign.api.MessageOrderFeginApi;
import com.sxpi.order.feign.api.OrderApiFeign;
import com.sxpi.order.service.mq.DeadOrderPublisher;
import com.sxpi.order.vo.BuyVo;
import com.sxpi.order.vo.CourseBuyVo;
import com.sxpi.order.vo.MessageOrderVo;
import com.sxpi.order.vo.OrderVo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: 董伟豪
 * @Date: 2022/10/25 20:10
 */
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {

    // kss-zixun-db-order datasource
    @Autowired
    private OrderApiFeign orderApiFeign;

    // kss-zixun-db-course datasource
    @Autowired
    private CourseApiFeign courseApiFeign;

    /**
     * 这里的注入已经交给spring的监听器去做了
     */
    @Autowired
    private MessageOrderFeginApi messageApiFeign;

    /**
     * 这里已经交给spring的异步处理做了
     */
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 这里我又交给的spring的aop去处理了
     */
    @Autowired
    private MessageAsyncService messageAsyncService;

    @Autowired
    private DeadOrderPublisher deadOrderPublisher;

    /**
     * 用户购买课程下课支付和扣减库存
     *
     *    @Transactional(rollbackFor = Exception.class) 普通事务回滚不可能起作用，因为只是针对于我当前项目的datasource，
     *      而这个是远程调用的数据库
     * @LcnTransaction 同一个数据源datasource
     * @param buyVo
     * @return
     */
    @Override
    // 这个注解好像和@TxTransactionn不一样，我加上@TxTransaction不得行
    // 分布式事务并不具备事务的回滚能力 他只是起到通知的作用 最终都是靠@Transactional来回滚
    // netty心跳机制
    // @LcnTransaction
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional
    public R buyCourse(BuyVo buyVo) {
        // 查询产品
        R courseDetail = courseApiFeign.getCourseDetail(buyVo.getOpid());
        // 我再controller转换成map了 -- 获取产品信息
        HashMap course = (HashMap) courseDetail.get("course");
        // 从course中取出realprice
        String realprice = MapUtil.getStr(course, "realprice");
        // 下单
        OrderVo orderVo = new OrderVo();
        BeanUtils.copyProperties(buyVo,orderVo);
        orderVo.setOrderNumber(System.currentTimeMillis()+"");
        orderVo.setPrice(new BigDecimal(realprice));
        R saveResultOrder = orderApiFeign.saveOrder(orderVo);

        // 扣减库存
        // 如果我这个用户已经把100w的单子下了，如果到了后面发消息错误？？？回滚以后 订单直接取消 100w打水漂
        CourseBuyVo courseBuyVo = new CourseBuyVo();
        courseBuyVo.setId(buyVo.getOpid());
        courseBuyVo.setNum(buyVo.getNum());
        R courseStoreResult = courseApiFeign.miniusCourseStore(courseBuyVo);
        Boolean flag = (Boolean) courseStoreResult.get("flag");
        if(!flag){
            throw new PugBusinessException("库存不足");
        }

        /**
         * 方式一；开辟新线程
         * - 1： 如果出现异常，你线程就终止了。如果异步逻辑非常的重要，那么是不是自己要去写补偿机制。
         * - 2：你怎么异步代码消息一定发送成功呢？所以你必须自己开发一个确认机制。
         * - 3：如果你并发量非常的大，new Thread是不停开辟对象，其实会大量消耗jvm的堆内存，居高不下，会造成jvm垃圾回收非常繁忙会遭大量停顿时间
         *     （jvm收集垃圾 stop word time），在停顿时间范围内，所有的线程会全部终止。就会出现很多程序执行失败，或者甚至造成堆内存溢出。解决
         *     方案自己开发一个线程池。
         *         ExecutorService executorService = Executors.newCachedThreadPool(); 但是依旧解决不了本质问题，还是得自己写补偿策略，还有确认机制
         * - 4： 你具有高可用吗？
         * 方式二：rabbitmq kafka rocketmq
         * 这样消息队列也是可以做分布式事务，但是这种基于XA方案的事务并不友好，但是如果想要达到数据的最终一致性的话，那倒是可以做
         */
/*
        // 我这里采用的是spring的监听器来分离业务的，但是我发现本质依旧还是要写很多的代码
        // 发消息（附属业务为什么要去回滚我主体业务？）
        MessageOrderVo messageOrderVo = new MessageOrderVo();
        messageOrderVo.setContent("保存了订单");
        // 把map属性传进去，然后把id取出来
        Long orderId = MapUtil.getLong(saveResultOrder, "id");
        messageOrderVo.setOrderId(orderId);
        messageOrderVo.setUserId(buyVo.getUserId());
        messageOrderVo.setUsername(buyVo.getUsername());
        messageOrderVo.setAvatar(buyVo.getAvatar());
        messageOrderVo.setOpid(buyVo.getOpid());
        String title = MapUtil.getStr(course, "title");
        messageOrderVo.setTitle(title);
        String img = MapUtil.getStr(course, "img");
        messageOrderVo.setImg(img);
        String desc = MapUtil.getStr(course, "description");
        messageOrderVo.setDescription(desc);
        messageOrderVo.setRealprice(realprice);
        // 删除耦合代码
        // messageApiFeign.saveMessageOrder(messageOrderVo);

        // 这里传递对象的方式有点多，还有修改OrderEvent的方式
        OrderEvent orderEvent = new OrderEvent(applicationContext);
        orderEvent.setMessageOrderVo(messageOrderVo);
        // 时间发布
        applicationContext.publishEvent(orderEvent);

*/
        // 数据汇总
        Map<String, Object> map = new HashMap<>();
        map.putAll(BeanUtil.beanToMap(buyVo));
        map.putAll(BeanUtil.beanToMap(course));
        map.putAll(BeanUtil.beanToMap(saveResultOrder));
        // 传递给aop后置通知来处理 ,我这里直接升级到了 @Returning的通知
        // MessageAspect.mapMessageThreadLocal.set(map);

        // 扣减优惠卷
        // 邮件发送
        // 订单—–派单—运单—通知骑手

        // 用户下单成功
        Long orderId = MapUtil.getLong(saveResultOrder, "id");
        deadOrderPublisher.sendMessage(orderId);


        // 成功给spring的@Returing数据
        return R.ok(map);
    }

    /**
     * 查询订单信息
     * @param orderNo
     * @return
     */
    @Override
    public R getOrderInfoByOrderInfo(String orderNo) {
        return orderApiFeign.getOrderInfoByOrderNo(orderNo);
    }

    public static void main(String[] args) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",12312312321L);
        map.put("name","yykk");
        String name = MapUtil.getStr(map, "name");
        System.out.println(name);
        Object name1 = map.get("name");
        System.out.println(name1);
    }

}
